/*
*  Impp - Instant Messenger
*
*  Copyright (C) 2008  Oliver Schneider
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
#include "SrvPackage.h"
#include <wx/log.h>
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include "../../core/Buddy.h"
#include "../../core/Impp.h"
#include "OscarProtData.h"

namespace Impp {

SrvPackage::SrvPackage(Flap & flap) :
	pos(0),size(flap.getDataSize()),data(flap.getData())
{

}

SrvPackage::SrvPackage(Tlv & tlv) :
	pos(0),size(tlv.length),data(tlv.data)
{}

SrvPackage::SrvPackage(size_t size, byte* data) :
	pos(0),size(size),data(data)
{}

SrvPackage::~SrvPackage()
{
}

byte SrvPackage::readByte(){
	if(pos < size ){
		return 	data[pos++];
	}
	outOfBounds();
	return 0x00;
}

word SrvPackage::readWord(){
	if(pos+1 < size){
	    word w(0);
	    w |= data[pos++];
	    w <<= 8;
	    w |= data[pos++];
	    return w;
	}
	outOfBounds();
	return 0x00;
}

word SrvPackage::readWordLE(){
	if(pos+1 < size){
	    word w(0);
	    w |= data[pos+1];
	    w <<= 8;
	    w |= data[pos];
	    pos+=2;
	    return w;
	}
	outOfBounds();
	return 0x00;
}
dword SrvPackage::readDWord(){
	if(pos+3 < size){
	    dword dw(0);
	    dw |= data[pos++];
	    dw <<= 8;
	    dw |= data[pos++];
	    dw <<= 8;
	    dw |= data[pos++];
	    dw <<= 8;
	    dw |= data[pos++];
	    return dw;
	}
	std::cout << "in dword: ";
	outOfBounds();
	return 0x00;
}

qword SrvPackage::readQWord(){
	if(pos+7 < size){
	    qword qw(0);
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    qw <<= 8;
	    qw |= data[pos++];
	    return qw;
	}
	std::cout << "in qword: ";
	outOfBounds();
	return 0x00;
}

dword SrvPackage::readDWordLE(){
	if(pos+3 < size){
	    dword dw(0);
	    dw |= data[pos+3];
	    dw <<= 8;
	    dw |= data[pos+2];
	    dw <<= 8;
	    dw |= data[pos+1];
	    dw <<= 8;
	    dw |= data[pos];
	    pos+=4;
	    return dw;
	}
	outOfBounds();
	return 0x00;
}
void SrvPackage::readString(char* str,size_t lng){
	if(pos+lng <= size){
		memcpy(str,data+pos,lng);
		str[lng]=0;
		pos+=lng;
		return;
	}
	outOfBounds();
	return;
}

wxString SrvPackage::readString(size_t lng){
	if(pos+lng <= size){
		char* tmp = new char[lng+1];
		memcpy(tmp,data+pos,lng);
		tmp[lng]=0;
		wxString ret = wxString::FromAscii((char*)tmp);
		delete[] tmp;
		pos+=lng;
		return ret;
	}
	outOfBounds();
	return wxEmptyString;
}

byte* SrvPackage::readData(size_t lng){
	if(pos+lng <= size){
		byte* d = (byte*) data+pos;
		pos+=lng;
		return d;
	}
	outOfBounds();
	return NULL;
}

void SrvPackage::readArray(byte *d, size_t lng){
	byte* dp = readData(lng);
	for (size_t i = 0; i < lng; ++i) {
		*d++ = *dp++;
	}
}

void SrvPackage::pushIn(std::vector<byte> & v, size_t lng){
	byte* dp = readData(lng);
	for (size_t i = 0; i < lng; ++i) {
		v.push_back(*dp++);
	}
}


Tlv SrvPackage::readTLV(){
	Tlv tlv;
	tlv.type=readWord();
	tlv.length=readWord();
	tlv.data=readData(tlv.length);
	return tlv;
}

Snac SrvPackage::readSnac(){
	Snac snac;
	snac.family=readWord();
	snac.subtype=readWord();
	snac.flags=readWord();
	snac.requestId=readDWord();
	return snac;
}


bool SrvPackage::hasBytesLeft(){
	return pos < size;
}
size_t SrvPackage::bytesLeft(){
	return size - pos;
}

void SrvPackage::outOfBounds(){
	wxLogDebug(wxT("SrvPackage::outOfBounds: No Data Avaibily: Pos: %i Size: %i"),pos,size);
}

void SrvPackage::readAllocedItems(size_t anz,std::vector<Item>* out){
	//std::cout << "ItemAnz: "<<  anz << std::endl;

	for (size_t i = 0; i < anz; i++) {

		Item item;

		size_t itemNameLng = readWord();

		item.itemName = readString(itemNameLng);
		item.groupId = readWord();
		item.itemId = readWord();
		item.typeOfItems = readWord();

		word itemDataLength = readWord();

		for(size_t j=0; j < itemDataLength;){
			Tlv tlv = readTLV();
			toAllocedTlv(&tlv);
			j+=4+tlv.length;
			item.data.push_back(tlv);
		}

		out->push_back(item);

	}
}

UserInfo SrvPackage::readUserInfos(Oscar* oscar, Buddy** b, wxString* statusMsg, bool* requestAvatar, Status* status){
	Buddy* &buddy = *b;

	UserInfo info;

	byte uinLng = readByte();
	info.uin=  readString(uinLng);
	/*dword warningLevel= */  readWord();
	dword numberOfTlvs = readWord();

	buddy = Impp::exemplar()->getBuddy(oscar->getAccount()->id, info.uin);

	*requestAvatar=false;
	*status = UNKNOWN;

	info.status = ICQ_STATUS_OFFLINE;//ICQ_STATUS_ONLINE; //TODO: icq2go
	info.onlineTime = 0;
	info.userClass = 0;
	info.ip = 0;
	info.accountData = 0;
	info.onlineTimeData = 0;
	info.icqmood = -1;
	info.idleTime = 0;
	info.newIdleTime=true;

	bool hasAva=false;

	for(size_t i=0; i < numberOfTlvs; ++i){
		Tlv tlv = readTLV();
		SrvPackage tlvData(tlv);

		switch(tlv.type){
			case 0x01:
				info.userClass=tlvData.readWord();
			break;
			case 0x03:
				info.onlineTime=tlvData.readDWord();
			break;
			case 0x04:
			{
				word idleMinutes = tlvData.readWord();
				info.idleTime=wxDateTime::GetTimeNow()-(time_t)idleMinutes*60;
				//wxLogDebug(wxT("idletime was %i"),idleMinutes);

			}
			break;
			case 0x06:
				tlvData.readWord();
				info.status=(ICQStatus) tlvData.readWord();
				*status = icqStatusToStatus(info.status);
			break;
			case 0x0A:
				info.ip=tlvData.readDWord();
			break;
			case 0x0D:
			{
				size_t lng = tlvData.bytesLeft()/16;
				for(size_t c=0; c < lng; c++){
					std::vector<byte> cap;
					tlvData.pushIn(cap,16);
					info.capabilities.push_back(cap);
				}
				info.newCaps=true;
			}

			break;
			case 0x1D:





				//tlvData.logToFile(wxT("log.txt"));


				//tlvData.logToStdOut();





				while(tlvData.hasBytesLeft()){

					word itemId = tlvData.readWord();
					byte itemFlag = tlvData.readByte();
					byte itemLng = tlvData.readByte();

					switch (itemId) {
						case 0x000C: //ICQ contact photo (16 bytes)
						//case 0x0008: //ICQ Flash avatar hash (16 bytes)
						case 0x0001: //AIM/ICQ avatar ID/hash (len 5 or 16 bytes)
						{
							info.avatar.id = itemId;
							info.avatar.flags = itemFlag;
							info.avatar.hasLng = itemLng;

							if(itemLng == 16){
								tlvData.readArray(info.avatar.hash,itemLng);
								hasAva=true;
								wxLogTrace(wxT("AVATAR"),wxT("SrvPackage::readUserInfos: Avatarinfo: id: %i, flags: %i, lng: %i"),info.avatar.id,info.avatar.flags,info.avatar.hasLng);

								if(buddy!=NULL){
									wxString curHash = buddy->getExtendedInfo(BUDDY_EXT_AVATARHASH);
									if(curHash.IsEmpty()){
										*requestAvatar = true;
										//wxLogDebug(wxT("SrvPackage::readUserInfos: no new hash"));
									}else{
										wxString newHash = toHexString((byte*) &info.avatar.hash, itemLng);
										//wxLogDebug(wxT("Avatar hash is: %s"), newHash.c_str());
										if(curHash != newHash){
											*requestAvatar = true;
											//wxLogDebug(wxT("SrvPackage::readUserInfos: %s hash was different! %s -- %s"), buddy->getName().c_str(), curHash.c_str(), newHash.c_str());
										}else{
											;
											//wxLogDebug(wxT("SrvPackage::readUserInfos: Hash was the same!!!: 1: %s, 2: %s"), curHash.c_str(), newHash.c_str());
										}
									}


								}
							}else{
								tlvData.skip(info.avatar.hasLng);


							}


						}

						break;
						case 0x0002:	//status message
						{
							word msgLng = tlvData.readWord();
							byte* awayMsg = tlvData.readData(msgLng);

							byte encodingLng = 0;
							wxString statusMsgEncoding;

							word encoding = tlvData.readByte();
							if(encoding == 0x01){
								byte encodingLng = tlvData.readByte();
								statusMsgEncoding = tlvData.readString(encodingLng);
							}

							wxString away = wxString::FromUTF8((char*)awayMsg,msgLng);

							if(away.IsEmpty()){
								char* tmp = new char[msgLng+1];
								memcpy(tmp,awayMsg,msgLng);
								tmp[msgLng]=0;
								away = wxString::FromAscii((char*)tmp);
								delete[] tmp;
							}

							if(statusMsg!=NULL){
								*statusMsg = away;
							}else{
								wxLogDebug(wxT("SrvPackage::readUserInfos: there is a status msg, but it is not used."));
							}

							tlvData.skip(itemLng-msgLng-encodingLng-3);

						}
						break;
						case 0x000D:	//last online message update time
						{
							tlvData.skip(itemLng);
						}
						break;
						case 0x000E:	//custom status / x-status
						{
							wxString customStatus;
							if(itemLng != 0){
								//extra status / icqmood set
								customStatus = tlvData.readString(itemLng);

								wxString num;
								if(customStatus.StartsWith(wxT("icqmood"),&num)){
									long int inum;
									if(num.ToLong(&inum)){
										//wxLogDebug(wxT("woud set to: mood: %i"),inum);
										info.icqmood = inum;
									}else{
										wxLogDebug(wxT("Error parsing icqmood, String was: %s"),customStatus.c_str());
									}
								}

							}else{
								;
							}
						}
						break;
						default:
						{
							wxLogDebug(wxT("SrvPackage::readUserInfos: unhandled itmeId: %x , continue"),itemId);
							//tlvData.logToStdOut(),
							tlvData.skip(itemLng);
						}

						break;
					}//switch




				}//while has left
			break;
		}
	}

	if(!hasAva){
		info.avatar.id = 0;
		info.avatar.flags = 0;
		info.avatar.hasLng = 0;
		for(int i=0; i < 16; i++){
			info.avatar.hash[i]=0;
		}
	}

	return info;
}

void SrvPackage::skip(size_t lng){
	if(pos+lng <= size){
		pos+=lng;
		return;
	}
	outOfBounds();
}

void SrvPackage::logToFile(const wxString & filename){
	wxFile file(filename, wxFile::write_append);
	wxFileOutputStream s(file);
	wxTextOutputStream out(s);

	wxString tmp;
	wxString chars;

	out << wxT("\n----------------------\n");
	size_t i;

	for(i = 0; i < size; ++i){
		tmp.Empty();
		tmp.Printf(wxT("%X "), data[i]);
		if(tmp.Length()==2){
			tmp = wxString(wxT("0")) << tmp;

		}

		if(data[i] >= 0x20 && data[i] <= 0x7D ){
			char tmp[2];
			tmp[0]=*(data+i);
			tmp[1]=0;
			chars.Append(wxString::Format(wxT("%s"),tmp));
		}else{
			chars.Append(wxT("."));
		}


		out << tmp;

		if(i%16==15){
			out << chars;
			out << wxT("\n");
			chars.Empty();
		}else if(i%8==7){
			out << wxT(" ");
		}



	}

	i = i%16-1;

	for(;i < 16; i++){
		out << wxT("   ");
	}

	out << wxT(" ") << chars;

	s.Close();


}

void SrvPackage::logRemainingToStdOut(){
	wxString out;


	wxString tmp;
	wxString chars;

	out << wxT("\n");
	size_t i;

	for(i = 0; i < size-pos; ++i){
		tmp.Empty();
		tmp.Printf(wxT("%X "), data[i+pos]);
		if(tmp.Length()==2){
			tmp = wxString(wxT("0")) << tmp;

		}

		if(data[i+pos] >= 0x20 && data[i+pos] <= 0x7D ){
			char tmp[2];
			tmp[0]=*(data+i+pos);
			tmp[1]=0;
			chars.Append(wxString::Format(wxT("%s"),tmp));
		}else{
			chars.Append(wxT("."));
		}


		out << tmp;

		if(i%16==15){
			out << chars;
			out << wxT("\n");
			chars.Empty();
		}else if(i%8==7){
			out << wxT(" ");
		}
	}

	i = i%16-1;

	for(;i < 16; i++){
		out << wxT("   ");
	}

	out << wxT(" ") << chars;
	out << wxT("\n");

	std::cout << out << std::endl;
}

void SrvPackage::logToStdOut(){
	wxString out;


	wxString tmp;
	wxString chars;

	out << wxT("\n");
	size_t i;

	for(i = 0; i < size; ++i){
		tmp.Empty();
		tmp.Printf(wxT("%X "), data[i]);
		if(tmp.Length()==2){
			tmp = wxString(wxT("0")) << tmp;

		}

		if(data[i] >= 0x20 && data[i] <= 0x7D ){
			chars.Append(wxString::Format(wxT("%c"),data[i]));
		}else{
			chars.Append(wxT("."));
		}


		out << tmp;

		if(i%16==15){
			out << chars;
			out << wxT("\n");
			chars.Empty();
		}else if(i%8==7){
			out << wxT(" ");
		}
	}

	i = i%16-1;

	for(;i < 16; i++){
		out << wxT("   ");
	}

	out << wxT(" ") << chars;
	out << wxT("\n");

	wxLogDebug(out);
}


}

